<?php

//define('DEBUG_HTML_DOM', true);

HTML::XHTML($config['xhtml']);

class HTML {

	protected static $_xhtml;

	public static function XHTML($val = null) {
		if (is_null($val)) {
			return self::$_xhtml;
		} else {
			self::$_xhtml = !!$val;
		}
	}

	public static function EntityEncode($str) {
		return htmlentities($str, ENT_COMPAT, strtoupper($GLOBALS['config']['charset']), false);
	}

	public static function EntityDecode($str) {
		return html_entity_decode($str, ENT_COMPAT, strtoupper($GLOBALS['config']['charset']));
	}

	public static function Parse($html) {
		return new HTMLDocument(@DOMDocument::LoadHTML($html));
	}

	public static function Text($text) {
		return new HTMLTextNode($text);
	}

	public static function BR() {
		$tag = self::Tag('br');
		//$tag->setSelfClosing(true);
		return $tag;
	}

	public static function P($inner, $attrs = array()) {
		return self::Tag('p', $inner, $attrs);
	}

	public static function H($level, $inner, $attrs = array()) {
		if (!is_numeric($level)) {
			return;
		}

		$level = (int)$level;
		if ($level < 1 || $level > 6) {
			return;
		}

		$h = 'h' . $level;
		$tag = self::Tag($h, $inner, $attrs);

		return $tag;
	}

	public static function Image($src, $attrs = array()) {
		$attrs['src'] = $src;
		$tag = self::Tag('img', null, $attrs);
		//$tag->setSelfClosing(true);
		return $tag;
	}

	public static function Script($src, $external = true, $type = false) {
		$attrs = array('type' => $type ? $type : 'text/javascript');

		if ($external) {
			$attrs['src'] = $src;
			return self::Tag('script', null, $attrs);
		} else {
			$inner = "\n\\\\<![CDATA[\n" . $src . "\n\\\\]]>\n";
			return self::Tag('script', $inner, $attrs);
		}
	}

	public static function Input($type, $name, $value = null, $attrs = array()) {
		$attrs['type'] = $type;
		$attrs['name'] = $name;

		if ($type != 'textarea') {
			$attrs['value'] = $value;
			$tag = self::Tag('input', null, $attrs);
			//$tag->setSelfClosing(true);
			return $tag;
		} else if ($type == 'textarea' && !is_null($value)) {
			return self::Tag('input', $value, $attrs);
		}
	}

	public static function Label($for, $label, $attrs = array()) {
		$attrs['for'] = $for;
		return self::Tag('label', $label, $attrs);
	}

	public static function Link($href, $inner, $attrs = array()) {
		global $config;

		if (isset($attrs['target']) && $config['xhtml']) {
			if (in_array($attrs['target'], array('_blank', '_newtab')) && !isset($attrs['rel'])) {
				unset($attrs['target']);
				$attrs['rel'] = 'external';
				trigger_error("Attribute target=\"_blank\" is not XHTML compliant, and has replaced with rel=\"external\".", E_USER_WARNING);
			} else {
				unset($attrs['target']);
				trigger_error("Target attribute is not XHTML compliant, and has been removed.", E_USER_WARNING);
			}
		}

		$attrs['href'] = $href;

		return self::Tag('a', $inner, $attrs);
	}

	public static function Div($inner = null, $attrs = array()) {
		return self::Tag('div', $inner, $attrs);
	}

	public static function Span($inner = null, $attrs = array()) {
		return self::Tag('span', $inner, $attrs);
	}

	public static function Pre($text, array $attrs = array()) {
		return self::Tag('pre', $text, $attrs);
	}

	public static function Table($data, $attrs = array()) {
		return self::Tag('table', $data, $attrs);
	}

	public static function Tag($type, $inner = null, $attrs = array()) {
		$tag = new HTMLElement($type);

		$tag->setAttributes($attrs);

		if (!is_null($inner)) {
			if (is_array($inner)) {
				foreach ($inner as $obj) {
					if (is_object($obj) && $obj instanceof IHTMLNode){
						$tag->appendChild($obj);
					} else if (is_scalar($obj) || is_callable(array($obj, '__toString'))) {
						$tag->appendChild(new HTMLTextNode((string)$obj));
					}
				}
			} else if (is_object($inner) && $inner instanceof IHTMLNode) {
				$tag->appendChild($inner);
			} else if (is_scalar($inner) || is_callable(array($inner, '__toString'))) {
				$tag->appendChild(new HTMLTextNode((string)$inner));
			}
		}

		return $tag;
	}

}

interface IHTMLNode {
	public function appendChild(&$childNode);
	public function removeChild(&$childNode);
	public function getElementById($id);
	public function getElementsByTagName($tagName);
}

class HTMLNode implements IHTMLNode {

	protected $_base;

	protected $_html = null;

	protected $_nodeName = null;
	protected $_parentNode = null;
	protected $_childNodes = array();
	protected $_attributes = array();

	public function getBase() {
		return $this->_base;
	}

	public function appendChild(&$childNode) {
		if ($childNode instanceof IHTMLNode) {
			$this->_base->appendChild($childNode->getBase());
		}
	}

	public function removeChild(&$childNode) {
		$this->_base->removeChild($childNode->getBase());
	}

	public function getElementById($id) {
		
	}

	public function getElementsByTagName($tagName) {
		
	}

	public function insertBefore() {
		
	}

	public function insertAfter() {
		
	}

	public function replaceNode() {
		
	}

	public function cloneNode($tree) {
		
	}

	public function __get($name) {
		switch ($name) {
			case 'innerHTML':
				return $this->_html;
			case 'nodeName':
				return $this->_nodeName;
				break;
			case 'nodeType':
				break;
			case 'nodeValue':
				break;
			case 'parentNode':
				return $this->_parentNode;
			case 'childNodes':
				return $this->_childNodes;
			case 'attributes':
				return $this->_attributes;
			case 'firstChild':
				return count($this->_childNodes) > 0 ? $this->_childNodes[0] : null;
			case 'lastChild':
				$count = count($this->_childNodes);
				return $count > 0 ? $this->_childNodes[$count-1] : null;
			default:
				return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
		}
	}

	public function __set($name, $value) {
		switch ($name) {
			case 'innerHTML':
				$this->_childNodes = HTML::Parse($value);
				$this->_html = $value;
				break;
			case 'nodeName':
			case 'nodeType':
			case 'nodeValue':
			case 'parentNode':
			case 'childNodes':
			case 'attributes':
			case 'firstChild':
			case 'lastChild':
				break;
			default:
				$this->_attributes[$name] = $value;
				break;
		}
	}

	public function renderHTML() {
	}

	public function __toString() {
		return $this->renderHTML();
	}

}

class HTMLDOMDocument extends HTMLNode {

	public function __construct($base = null) {
		$this->_base = is_null($base) ? new DOMDocument() : $base;
	}

	public function getElementsByClassName($classname) {
		
	}

	public function querySelectorAll($selector) {
		
	}

	public function renderHTML() {
		return HTML::XHTML() ? $this->_base->saveXML() : $this->_base->saveHTML();
		/*$this->_html = '';
		foreach ($this->_childNodes as $child) {
			$this->_html .= $child->renderHTML();
		}
		return $this->_html;*/
	}
}

class HTMLDOMDocumentFragment extends HTMLNode {

	public function __construct($base = null) {
		$this->_base = is_null($base) ? new DOMDocumentFragment() : $base;
	}

	public function getElementsByClassName($classname) {
		
	}

	public function querySelectorAll($selector) {
		
	}

	public function renderHTML() {
		return HTML::XHTML() ? $this->_base->saveXML() : $this->_base->saveHTML();
		/*$this->_html = '';
		foreach ($this->_childNodes as $child) {
			$this->_html .= $child->renderHTML();
		}
		return $this->_html;*/
	}
}

class HTMLElement extends HTMLNode {

	public function __construct($nodeName) {
		$this->_base = new DOMElement($nodeName);

		/*$this->_tag_name = $nodeName;
		$this->_nodeName = strtoupper($nodeName);*/

		/*if (isset(HTML::$emptyTags[strtolower($nodeName)])) {
			$this->_self_closing = true;
		}*/
	}

	public function getAttributes() {
		return $this->_base->attributes;
		//return $this->_attributes;
	}

	public function getAttribute($name) {
		return $this->_base->getAttribute($name);
		/*$name = strtolower($name);
		return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;*/
	}

	public function setAttributes($nvpairs) {
		foreach ($nvpairs as $n => $v) {
			$this->_base->setAttribute($n, $v);
		}
		//$this->_attributes = array_merge($this->_attributes, $nvpairs);
	}

	public function setAttribute($name, $value) {
		$this->_base->setAttribute($name, $value);
		//$name = strtolower($name);
		//$this->_attributes[$name] = $value;
	}

	public function setSelfClosing($value) {
		//$this->_self_closing = $value;
	}

	public function getSelfClosing() {
		//return $this->_self_closing;
	}

	public function renderHTML() {
		$tag = '<' . $this->_tag_name;
		foreach ($this->_attributes as $name => $value) {
			$tag .= ' ' . strtolower($name) . '="' . str_replace('"', '\'', $value) . '"';
		}

		if ($this->_self_closing) {
			//$tag .= self::getXHTML() ? ' />' : '>';
			$tag .= ' />';
		} else {
			$tag .= '>';
			foreach ($this->_childNodes as $child) {
				$tag .= (string)$child;
			}
			$tag .= '</' . $this->_tag_name . '>';
		}

		return $tag;
	}

	public function __toString() {
		return $this->renderHTML();
	}

}

class HTMLTextNode extends HTMLNode {
	//public $nodeValue;

	public function __construct($nodeValue) {
		$this->_base = new DOMText($nodeValue);
		//$this->nodeValue = $nodeValue;
	}

	public function renderHTML() {
		return $this->nodeValue;
	}
}

class URL {
	public static function Build($url, $query) {
		$q = array();
		foreach ($query as $name => $value) {
			$q[] = urlencode($name) . '=' . urlencode($value);
		}
		return $url . '?' . implode('&amp;', $q);
	}
}
